/* Simulates a unix-like file system.  Provides basic directory
   and file operations and implements them in terms of the underlying
   disk block structures.
*/

#include <io_types.h>
#include <FileDescriptor.h>
#include <Kernel.h>
#include <iostream>

using namespace std;


ProcessContext *Kernel::process;
const String Kernel::PROGRAM_NAME  = "Kernel"; // name for error msgs
int Kernel::processCount;
FileDescriptor *Kernel::openFiles[MaxOpenFiles]; 
FileSystem *Kernel::openFileSystems [MaxOpenFileSystems];  
int Kernel::MAX_OPEN_FILES;
const StringArr Kernel::sys_errlist = 
	  { 
	    null
	 , "Not owner"
	 , "No such file or directory"
	 , null
	 , null
	 , null
	 , null
	 , null
	 , null
	 , "Bad file number"
	 , null
	 , null
	 , null
	 , "Permission denied"
	 , null
	 , null
	 , null
	 , "File exists"
	 , "Cross-device link"
	 , null
	 , "Not a directory"
	 , "Is a directory"
	 , "Invalid argument"
	 , "File table overflow"
	 , "Too many open files"
	 , null
	 , null
	 , "File too large"
	 , "No space left on device"
	 , null
	 , "Read-only file system"
	 , "Too many links"
	  };

int Kernel::MAX_OPEN_FILE_SYSTEMS;
short Kernel::ROOT_FILE_SYSTEM = 0;

void Kernel::perror(String s)
{
  const char *message = null;
  if ((Kernel::process->errno > 0) && (Kernel::process->errno < Kernel::sys_nerr))
    message = Kernel::sys_errlist[Kernel::process->errno];
  if (message == null)
    fprintf (stderr, "%s%s%d\n", s," + : unknown errno ",Kernel::process->errno);
  else
    fprintf (stderr, "%s%s%s\n", s,":",  message);
}


void Kernel::setErrno(int newErrno)
{
  if(Kernel::process == null)
    {
      fprintf (stderr, "no current process in setErrno\n");
      exit (Kernel::EXIT_F);
    }
  Kernel::process->errno = newErrno;
}

int Kernel::getErrno()
{
    if(Kernel::process == null)
    {
      fprintf (stderr, "no current process in getErrno\n");
      exit (Kernel::EXIT_F);
    }
    return Kernel::process->errno;
}


int Kernel::close(int fd)
{
  // check fd
  int status = check_fd(fd);
  if(status < 0)
    return status;

  // remove the file descriptor from the kernel's list of open files
  for(int i = 0; i < Kernel::MAX_OPEN_FILES; i ++)
    if(openFiles[i] == process->openFiles[fd])
      {
        openFiles[i] = null;
        break;
      }
  // ??? is it an error if we didn't find the open file?

  // remove the file descriptor from the list.
  process->openFiles[fd] = null;
  process->num_files--;
  return 0;
}

int Kernel::creat(String pathname, short mode)
// throws Exception
{
  // get the full path
  String fullPath = getFullPath(pathname);

  StringBuffer *dirname = new StringBuffer("/");
  FileSystem *fileSystem = openFileSystems[ROOT_FILE_SYSTEM];
  IndexNode *currIndexNode = getRootIndexNode();
  IndexNode *prevIndexNode = null;
  short indexNodeNumber = FileSystem::ROOT_INDEX_NODE_NUMBER;

  StringTokenizer *st = new StringTokenizer(fullPath, "/");
  String name = "."; // start at root node
  while(st->hasMoreTokens())
    {
      name = st->nextToken();
      if (strcmp(name, ""))
	{
	  // check to see if the current node is a directory
	  if((currIndexNode->getMode() & S_IFMT) != S_IFDIR)
	    {
	      // return (ENOTDIR) if a needed directory is not a directory
	      process->errno = ENOTDIR;
	      return -1;
	    }
	  
	  // check to see if it is readable by the user
	  // ??? tbd
	  // return (EACCES) if a needed directory is not readable

	  if(st->hasMoreTokens())
	    {
	      dirname->append(name);
	      dirname->append('/');
	    }

	  // get the next inode corresponding to the token
	  prevIndexNode = currIndexNode;
	  currIndexNode = new IndexNode();
	  indexNodeNumber = findNextIndexNode
	    (fileSystem, prevIndexNode, name, currIndexNode);
	}
    }

  // ??? we need to set some fields in the file descriptor
  int flags = O_WRONLY; // ???
  FileDescriptor *fileDescriptor = null;

  if (indexNodeNumber < 0)
    {
      // file does not exist.  We check to see if we can create it.
      // check to see if the prevIndexNode (a directory) is writeable
      // ??? tbd
      // return (EACCES) if the file does not exist and the directory
      // in which it is to be created is not writable

      currIndexNode->setMode(mode);
      currIndexNode->setNlink((short)1);

      // allocate the next available inode from the file system
      short newInode = fileSystem->allocateIndexNode();
      if(newInode == -1)
        return -1;

      fileDescriptor = 
        new FileDescriptor(fileSystem, currIndexNode, flags);
      // assign inode for the new file
      fileDescriptor->setIndexNodeNumber(newInode);

      //      std::cout << "I'm here" << std::endl;

      fileSystem->writeIndexNode(currIndexNode, newInode);

      // open the directory
      // ??? it would be nice if we had an "open" that took an inode 
      // instead of a name for the dir
      
      int dir = open(dirname->toString(), O_RDWR);
      if(dir < 0)
	{
	  perror(PROGRAM_NAME);
	  exit(1);
	}

      // scan past the directory entries less than the current entry
      // and insert the new element immediately following
      int status;
      DirectoryEntry *newDirectoryEntry = 
        new DirectoryEntry(newInode, name);
      DirectoryEntry* currentDirectoryEntry = new DirectoryEntry();
      while(true)
	{
	  // read an entry from the directory
	  status = readdir(dir, currentDirectoryEntry);
	  if(status < 0)
	    {
	      fprintf (stderr, "error reading directory in creat\n");
	      exit(Kernel::EXIT_F);
	    }
	  else if(status == 0)
	    {
	      // if no entry read, write the new item at the current 
	      // location and break
	      writedir(dir, newDirectoryEntry);
	      break;
	    }
	  else
	    {
	      // if current item > new item, write the new item in 
	      // place of the old one and break
	      if(strcmp (currentDirectoryEntry->getName(),
			 newDirectoryEntry->getName()) > 0)
		{
		  int seek_status = 
		    lseek(dir, - DirectoryEntry::DIRECTORY_ENTRY_SIZE, 1);
		  if(seek_status < 0)
		    {
		      fprintf (stderr, ": error during seek in creat\n");
		      exit(Kernel::EXIT_F);
		    }
		  writedir(dir, newDirectoryEntry);
		  break;
		}
	    }
	}
      // copy the rest of the directory entries out to the file
      while (status > 0)
	{
	  DirectoryEntry *nextDirectoryEntry = new DirectoryEntry();
	  // read next item
	  status = readdir(dir, nextDirectoryEntry);
	  if(status > 0)
	    {
	      // in its place
	      int seek_status = 
		lseek(dir, - DirectoryEntry::DIRECTORY_ENTRY_SIZE, 1);
	      if(seek_status < 0)
		{
		  fprintf (stderr, ": error during seek in creat\n");
		  exit(Kernel::EXIT_F);
		}
	    }
	  // write current item
	  writedir(dir, currentDirectoryEntry);
	  // current item = next item
	  currentDirectoryEntry = nextDirectoryEntry;
	}

      // close the directory
      close(dir);
    }
  else
    {
      // file does exist (indexNodeNumber >= 0)

      // if it's a directory, we can't truncate it
      if((currIndexNode->getMode() & S_IFMT) == S_IFDIR)
	{
	  // return (EISDIR) if the file is a directory
	  process->errno = EISDIR;
	  return -1;
	}

      // check to see if the file is writeable by the user
      // ??? tbd
      // return (EACCES) if the file does exist and is unwritable

      // free any blocks currently allocated to the file
      int blockSize = fileSystem->getBlockSize();
      int blocks = (currIndexNode->getSize() + blockSize - 1)/blockSize;
      for(int i = 0; i < blocks; i ++)
	{
	  int address = currIndexNode->getBlockAddress(i);
	  if(address != FileSystem::NOT_A_BLOCK)
	    {
	      fileSystem->freeBlock(address);
	      currIndexNode->setBlockAddress(i, FileSystem::NOT_A_BLOCK);
	    }
	}
      
      // update the inode to size 0
      currIndexNode->setSize(0);

      // write the inode to the file system.
      fileSystem->writeIndexNode(currIndexNode, indexNodeNumber);

      // set up the file descriptor
      fileDescriptor = 
        new FileDescriptor(fileSystem, currIndexNode, flags);
      // assign inode for the new file
      fileDescriptor->setIndexNodeNumber(indexNodeNumber);
    }

  return open(fileDescriptor);
}


void Kernel::Exit(int status)
//  throws Exception
{
  // close anything that might be open for the current process
  for(int i = 0; i < process->num_files; i ++)
    if(Kernel::process->openFiles[i] != null)
      {
        close(i);
      }
  
  // terminate the process
  Kernel::process = null;
  Kernel::processCount --;

  // if this is the last process to end, call finalize
  if(Kernel::processCount <= 0)
    finalize(status);
}

int Kernel::lseek(int fd, int offset, int whence)
{
  // check fd
  int status = check_fd(fd);
  if(status < 0)
    return status;

  FileDescriptor *file = process->openFiles[fd];

  int newOffset;
  if(whence == 0)
    newOffset = offset;
  else if(whence == 1)
    newOffset = file->getOffset() + offset;
  else if (whence == 2)
    newOffset = file->getSize() + offset;
  else
    {
      // bad whence value
      process->errno = EINVAL;
      return -1;
    }

  if(newOffset < 0)
    {
      // bad offset value
      process->errno = EINVAL;
      return -1;
    }

  file->setOffset(newOffset);
  return newOffset;
}

int Kernel::open(String pathname, int flags)
  // throws Exception
{
  // get the full path name
  String fullPath = getFullPath(pathname); 

  IndexNode *indexNode = new IndexNode();
  short indexNodeNumber = findIndexNode(fullPath, indexNode);
  if(indexNodeNumber < 0)
    return -1;

  // ??? return (Exxx) if the file is not readable 
  // and was opened O_RDONLY or O_RDWR

  // ??? return (Exxx) if the file is not writable 
  // and was opened O_WRONLY or O_RDWR

  // set up the file descriptor
  FileDescriptor *fileDescriptor = new FileDescriptor
    (openFileSystems[ ROOT_FILE_SYSTEM ], indexNode, flags);
  fileDescriptor->setIndexNodeNumber(indexNodeNumber);

  return open(fileDescriptor);
}

int Kernel::open(FileDescriptor *fileDescriptor)
{
  // scan the kernel open file list for a slot 
  // and add our new file descriptor
  int kfd = -1;
  for(int i = 0; i < Kernel::MAX_OPEN_FILES; i ++)
    if(openFiles[i] == null)
      {
        kfd = i;
        openFiles[kfd] = fileDescriptor;
        break;
      }
  if(kfd == -1)
    { 
      // return (ENFILE) if there are already too many open files
      process->errno = ENFILE;
      return -1;
    }

  // scan the list of open files for a slot 
  // and add our new file descriptor
  int fd = -1;
  for(int i = 0; i < ProcessContext::MAX_OPEN_FILES; i ++)
    if(process->openFiles[i] == null)
      {
        fd = i;
        process->openFiles[fd] = fileDescriptor;
	process->num_files++;
        break;
      }
  if(fd == -1)
    {
      // remove the file from the kernel list
      openFiles[kfd] = null;
      // return (EMFILE) if there isn't room left
      process->errno = EMFILE;
      return -1;
    }

  // return the index of the file descriptor for now open file
  return fd;
}


int Kernel::read(int fd, byte *buf, int count)
// throws Exception
{
  // check fd
  int status = check_fd_for_read(fd);
  if(status < 0)
    return status;

  FileDescriptor *file = process->openFiles[fd];
  int offset = file->getOffset();
  int size = file->getSize();
  int blockSize = file->getBlockSize();
  byte* bytes = file->getBytes();
  int readCount = 0;
  for(int i = 0; i < count; i ++)
    {
      // if we read to the end of the file, stop reading
      if(offset >= size)
        break;
      // if this is the first time through the loop,
      // or if we're at the beginning of a block, load the data block
      if((i == 0) || ((offset % blockSize) == 0))
	{
	  status = file->readBlock((short)(offset / blockSize));
	  if(status < 0)
	    return status;
	}
      // copy a byte from the file buffer to the read buffer
      buf[i] = bytes[ offset % blockSize ];
      offset ++;
      readCount ++;
    }
    // update the offset
    file->setOffset(offset);

    // return the count of bytes read
    return readCount;
  }

int Kernel::readdir(int fd, DirectoryEntry *dirp) 
// throws Exception
{
  // check fd
  int status = check_fd_for_read(fd);
  if(status < 0)
    return status;

  FileDescriptor *file = process->openFiles[fd];

  // check to see if the file is a directory
  if((file->getMode() & S_IFMT) != S_IFDIR)
    {
      // return (ENOTDR) if a needed directory is not a directory
      process->errno = ENOTDIR;
      return -1;
    }

  // return 0 if at end of directory
  if(file->getOffset() >= file->getSize())
    return 0;

  // read a block, if needed
    status = file->readBlock((short)(file->getOffset() / file->getBlockSize()));
    if(status < 0)
      return status;

    // read bytes from the block into the DirectoryEntry
    dirp->read(file->getBytes(), 
      file->getOffset() % file->getBlockSize());
    file->setOffset(file->getOffset() + 
      DirectoryEntry::DIRECTORY_ENTRY_SIZE);

    // return the size of a DirectoryEntry
    return DirectoryEntry::DIRECTORY_ENTRY_SIZE;
  }

int Kernel::fstat(int fd, Stat *buf)
// throws Exception
{
  // check fd
  int status = check_fd(fd);
  if(status < 0)
    return status;

  FileDescriptor *fileDescriptor = process->openFiles[fd];
  short deviceNumber = fileDescriptor->getDeviceNumber();
  short indexNodeNumber = fileDescriptor->getIndexNodeNumber();
  IndexNode *indexNode = fileDescriptor->getIndexNode();

  // copy information to buf
  buf->st_dev = deviceNumber;
  buf->st_ino = indexNodeNumber;
  buf->copyIndexNode(indexNode);

  return 0;
}

int Kernel::stat(String name, Stat *buf)
// throws Exception
{
  // a buffer for reading directory entries
  DirectoryEntry *directoryEntry = new DirectoryEntry();

  // get the full path
  String path = getFullPath(name);

  // find the index node
  IndexNode *indexNode = new IndexNode();
  short indexNodeNumber = findIndexNode(path, indexNode); 
  if(indexNodeNumber < 0)
    {
      // return ENOENT
      process->errno = ENOENT;
      return -1;
    }

  // copy information to buf
  buf->st_dev = ROOT_FILE_SYSTEM;
  buf->st_ino = indexNodeNumber;
  buf->copyIndexNode(indexNode);

  return 0;
}

void Kernel::sync()
{
  // write out superblock if updated
  // write out free list blocks if updated
  // write out inode blocks if updated
  // write out data blocks if updated

  // at present, all changes to inodes, data blocks, 
  // and free list blocks
  // are written as they go, so this method does nothing.
}

int Kernel::write(int fd, byte *buf, int count)
// throws Exception
{
  // check fd
  int status = check_fd_for_write(fd);
  if(status < 0)
    return status;

  FileDescriptor *file = process->openFiles[fd];

  // return (ENOSPC) if the device containing the file system
  // referred to by fd has not room for the data

  int offset = file->getOffset();
  int size = file->getSize();
  int blockSize = file->getBlockSize();
  byte* bytes = file->getBytes();
  int writeCount = 0;
  for(int i = 0; i < count; i ++)
    {
      // if this is the first time through the loop,
      // or if we're at the beginning of a block, 
      // load or allocate a data block
      if((i == 0) || ((offset % blockSize) == 0))
      {
        status = file->readBlock((short)(offset / blockSize));
        if(status < 0)
          return status;
      }
      // copy a byte from the write buffer to the file buffer
      bytes[ offset % blockSize ] = buf[i];
      offset ++;
      // if we get to the end of a block, write it out
      if((offset % blockSize) == 0)
	{
	  status = 
	    file->writeBlock((short)((offset - 1) / blockSize));
	  if(status < 0)
	    return status;
	  // update the file size if it grew
	  if(offset > size)
	    {
	      file->setSize(offset);
	      size = offset;
	    }
	}
      writeCount ++;
    }

  // write the last block if we wrote anything to it
  if((offset % blockSize) > 0)
    {
      status = file->writeBlock((short)((offset - 1) / blockSize));
      if(status < 0)
        return status;
    }
    
  // update the file size if it grew
  if(offset > size)
    file->setSize(offset);

  // update the offset
  file->setOffset(offset);

  // return the count of bytes written
  return writeCount;
}

int Kernel::writedir(int fd, DirectoryEntry *dirp) 
// throws Exception
{
  // check fd
  int status = check_fd_for_write(fd);
  if(status < 0)
    return status;

  FileDescriptor *file = process->openFiles[fd];

  // check to see if the file is a directory
  if((file->getMode() & S_IFMT) != S_IFDIR)
    {
      // return (ENOTDIR) if a needed directory is not a directory
      process->errno = ENOTDIR;
      return -1;
    }

  short blockSize = file->getBlockSize();
  // allocate or read a block
  status = file->readBlock((short)(file->getOffset() / blockSize));
  if(status < 0)
    return status;

  // write bytes from the DirectoryEntry into the block
  dirp->write(file->getBytes(), file->getOffset() % blockSize);

  // write the updated block
  status = file->writeBlock((short)(file->getOffset() / blockSize));
  if(status < 0)
    return status;

  // update the file size
  file->setOffset(file->getOffset() + 
		  DirectoryEntry::DIRECTORY_ENTRY_SIZE);
  if(file->getOffset() > file->getSize())
    file->setSize(file->getOffset());

  // return the size of a DirectoryEntry
  return DirectoryEntry::DIRECTORY_ENTRY_SIZE;
}

/*
to be done:
       int access(const char *pathname, int mode);
       int link(const char *oldpath, const char *newpath);
       int unlink(const char *pathname);
       int rename(const char *oldpath, const char *newpath);
       int symlink(const char *oldpath, const char *newpath);
       int lstat(const char *file_name, struct stat *buf);
       int chmod(const char *path, mode_t mode);
       int fchmod(int fildes, mode_t mode);
       int chown(const char *path, uid_t owner, gid_t group);
       int fchown(int fd, uid_t owner, gid_t group);
       int lchown(const char *path, uid_t owner, gid_t group);
       int utime(const char *filename, struct utimbuf *buf);
       int readlink(const char *path, char *buf, size_t bufsiz);
       int chdir(const char *path);
       mode_t umask(mode_t mask);
*/

void Kernel::initialize()
{
  // We are hard-setting the properties ... could also check filesys.conf
    // get the root file system properties
    String rootFileSystemFilename = "filesys.dat";
    String rootFileSystemMode = "rw";
    short uid = 1;
    uid = 1;
    short gid = 1;
    short umask = 0002;
    String dir = "/root";
    MAX_OPEN_FILES = 20;
    ProcessContext::MAX_OPEN_FILES = 10;

    // create open file array
    for (int i=0; i<MAX_OPEN_FILES; i++) {
      openFiles[i] = new FileDescriptor;
      openFiles[i] = null;
    }

    // create the first process
    Kernel::process = new ProcessContext(uid, gid, dir, umask);
    Kernel::processCount++;
    
    // open the root file system -- should catch error if it fails!
    openFileSystems[ROOT_FILE_SYSTEM] = 
      new FileSystem(rootFileSystemFilename, rootFileSystemMode);
}


void Kernel::finalize(int status)
//  throws Exception
{
  // exit() any remaining processes
  if(Kernel::process != null)
    Exit(0);

  // flush file system blocks
  sync();

  // close the root file system
  openFileSystems[ROOT_FILE_SYSTEM]->close();

  // terminate the program
  exit (status);
}

/* Some internal methods. */

int Kernel::check_fd(int fd)
{
  // look for the file descriptor in the open file list
  if (fd < 0 || 
      fd >= Kernel::process->num_files || 
      Kernel::process->openFiles[fd] == null)
    {
      // return (EBADF) if file descriptor is invalid
      Kernel::process->errno = EBADF;
      return -1;
    }

  return 0;
}

int Kernel::check_fd_for_read(int fd)
{
  int status = check_fd(fd);
  if(status < 0)
    return -1;

  FileDescriptor *fileDescriptor = Kernel::process->openFiles[fd];
  int flags = fileDescriptor->getFlags();
  if((flags != O_RDONLY) && 
     (flags != O_RDWR))
    {
      // return (EBADF) if the file is not open for reading
      Kernel::process->errno = EBADF;
      return -1;
    }

  return 0;
}

int Kernel::check_fd_for_write(int fd)
{
  int status = check_fd(fd);
  if(status < 0)
    return -1;

  FileDescriptor *fileDescriptor = Kernel::process->openFiles[fd];
  int flags = fileDescriptor->getFlags();
  if((flags != O_WRONLY) && 
     (flags != O_RDWR))
    {
      // return (EBADF) if the file is not open for writing
      Kernel::process->errno = EBADF;
      return -1;
    }

  return 0;
}

String Kernel::getFullPath(String pathname)
{
  String fullPath = null;

  // make sure the path starts with a slash
  //    if(pathname.startsWith("/"))
  if(pathname[0] ==  '/')
    fullPath = pathname;
  else {
    char *temp = new char[100]; // should clean up ... use StringBuffer
    strcpy (temp, Kernel::process->getDir());
    strcat (temp, "/");
    fullPath = strcat (temp, pathname);
  }
  return fullPath;
}

IndexNode *Kernel::rootIndexNode = null;
IndexNode *Kernel::getRootIndexNode()
{
  if(rootIndexNode == null)
    rootIndexNode = openFileSystems[ROOT_FILE_SYSTEM]->getRootIndexNode();
  return rootIndexNode;
}

// get the inode for a file which is expected to exist
short Kernel::findIndexNode(String path, IndexNode *inode)
//  throws Exception
{
  // start with the root file system, root inode
  FileSystem *fileSystem = openFileSystems[ ROOT_FILE_SYSTEM ];
  IndexNode *indexNode = getRootIndexNode();
  short indexNodeNumber = FileSystem::ROOT_INDEX_NODE_NUMBER;

  // parse the path until we get to the end
  StringTokenizer *st = new StringTokenizer(path, "/");
  while(st->hasMoreTokens())
    {
      //      char* c = (char*)malloc(strlen(st->nextToken())+2);
      String s = st->nextToken();
      /*
      strcat(c,"/");
      strcat(c,s);
      int fd1 = open(c,0);
      DirectoryEntry* de = new DirectoryEntry();
      readdir(fd1,de);
      int inode_num = de->getIno();
      fileSystem->readIndexNode(indexNode,inode_num);
      */

      if (strcmp (s,""))
      {
	//	fprintf(stdout, "%d %d\n", indexNode->getMode() & S_IFMT, S_IFDIR);

        // check to see if it is a directory
        if((indexNode->getMode() & S_IFMT) != S_IFDIR)
        {
          // return (ENOTDIR) if a needed directory is not a directory
          Kernel::process->errno = ENOTDIR;
          return -1;
        }
	//	std::cout << "here " << indexNode->getMode() << " " << (indexNode->getMode() & S_IFMPB) << " " << S_IFSYM << " " << S_IFDIR << std::endl;
	if ((indexNode->getMode() & S_IFMPB) == S_IFSYM) {
	  //	  std::cout << "Symlink! (inode)" << std::endl;
	    int in_fd = Kernel::open(path, Kernel::O_RDONLY);
	    if (in_fd < 0) {
	      Kernel::perror(PROGRAM_NAME);
	      fprintf(stderr, "%s%s%s\n", PROGRAM_NAME, ": unable to open symlink", path);
	      Kernel::Exit(2);
	    }

	    byte *buffer = new byte[256];
	    int rd_count = Kernel::read(in_fd, buffer, 256);

	    Kernel::close(in_fd);

	    return findIndexNode((String) buffer, inode);
	  }

        // check to see if it is readable by the user
        // ??? tbd
        // return (EACCES) if a needed directory is not readable

        IndexNode *nextIndexNode = new IndexNode();
        // get the next index node corresponding to the token
        indexNodeNumber = findNextIndexNode
	  (fileSystem, indexNode, s, nextIndexNode);
        if(indexNodeNumber < 0)
        {
          // return ENOENT
          Kernel::process->errno = ENOENT;
          return -1;
        }
        indexNode = nextIndexNode;
      }
    }
  // copy indexNode to inode
  indexNode->copy(inode);
  return indexNodeNumber;
}

short Kernel::findNextIndexNode
(FileSystem *fileSystem, IndexNode *indexNode, String name, 
 IndexNode *nextIndexNode)
// throws Exception
{
  // if stat isn't a directory give an error
  if((indexNode->getMode() & S_IFMT) != S_IFDIR)
    {
      // return (ENOTDIR) if a needed directory is not a directory
      Kernel::process->errno = ENOTDIR;
      return -1;
    }

  // if user isn't alowed to read directory, give an error
  // ??? tbd
  // return (EACCES) if a needed directory is not readable

  FileDescriptor *fileDescriptor = 
    new FileDescriptor(fileSystem, indexNode, O_RDONLY);
  int fd = open(fileDescriptor);
  if(fd < 0)
    {
      // process->errno = ???
      return -1;
    }

  // create a buffer for reading directory entries
  DirectoryEntry *directoryEntry = new DirectoryEntry();

  int status = 0;
  int close_status;
  short indexNodeNumber = -1;
  // while there are more directory blocks to be read
  while(true)
    {
      // read a directory entry
      status = readdir(fd, directoryEntry);
      if(status <= 0)
      {
        // we got to the end of the directory, or 
        // encountered an error, so quit
        break;
      }
      if(!strcmp (directoryEntry->getName(), name))
      {
        indexNodeNumber = directoryEntry->getIno();
        // read the inode block
        fileSystem->readIndexNode(nextIndexNode, indexNodeNumber);
        // we're done searching
        break;
      }
    }

    // close the file since we're done with it
    close_status = close(fd);
    if(close_status < 0)
    {
      // process->errno = ???
      return -1;
    }

    // if we encountered an error reading, return error
    if(status < 0)
    {
      // process->errno = ???
      return -1;
    }

    // if we got to the directory without finding the name, return error
    if(status == 0)
    {
      Kernel::process->errno = ENOENT;
      return -1;
    }

    // return index node number if success
    return indexNodeNumber;
}

int Kernel::link(String originalPath, String newPath){

     String PROGRAM_NAME = "ln";
     short OUTPUT_MODE = 0700;

     std::string fileName;

     // create std::string from newPath
     std::string pathStr(newPath);

     size_t pos = pathStr.find_last_of('/');

     // extract file name from newPath
     if(pos != std::string::npos)
     {
        fileName.assign(pathStr.begin() + pos + 1, pathStr.end());
     }

     else
     {
        fileName = pathStr;
     }

     // get newPath's parent directory
     pathStr = pathStr.substr(0, pos);

     // convert std::string to char*
     String parentDir = new char[pathStr.length() + 1];
     strcpy(parentDir, pathStr.c_str());

     // convert std::string to char*
     String newFileName = new char[fileName.length() + 1];
     strcpy(newFileName, fileName.c_str());

     Stat *statOriginalPath = new Stat();
     Stat *statParentDir = new Stat();

     // stat the name to get information about the file or directory
     short statusOriginalPath = Kernel::stat(originalPath, statOriginalPath);
     short statusParentDir = Kernel::stat(parentDir, statParentDir);

     // exit if error
     if((statusOriginalPath || statusParentDir) < 0)
     {
        Kernel::perror(PROGRAM_NAME);
	Kernel::Exit(1);
     }

     // mask the file type from the mode for originalPath
     short typeOriginalPath = (short)(statOriginalPath->getMode() & Kernel::S_IFMT);

     // mask the file type from the mode for parentDir
     short typeParentDir = (short)(statParentDir->getMode() & Kernel::S_IFMT);

     // if originalPath is a directory, exit
     if(typeOriginalPath == Kernel::S_IFDIR)
     {
        cout << "Error: Original Path is a Directory" << endl;
	Kernel::perror(PROGRAM_NAME);
	Kernel::Exit(1);
     }

     // if originalPath is a file, and newPath's parent directory is valid, continue.
     else if(typeParentDir == Kernel::S_IFDIR)
     {
       
        // get inode # of original file
        IndexNode* indexNode = new IndexNode();
        short indexNodeNumber = Kernel::findIndexNode(originalPath, indexNode);

        if(indexNodeNumber < 0)
        {
            Kernel::perror(PROGRAM_NAME);
            Kernel::Exit(1);
        }

        FileSystem *fileSystem = openFileSystems[ROOT_FILE_SYSTEM];

        int flags = O_WRONLY;
        FileDescriptor *fileDescriptor = null;

        fileDescriptor = new FileDescriptor(fileSystem, indexNode, flags);
        // assign inode for the new file
        fileDescriptor->setIndexNodeNumber(indexNodeNumber);
        
        // open the directory
        int dir = open(parentDir, O_RDWR);
 
        cout << parentDir << endl;
        if(dir < 0)
        {
	    Kernel::perror(PROGRAM_NAME);
            Kernel::Exit(1);
        }

        // scan past the directory entries less than the current entry
        // and insert the new element immediately following
        int status;
        DirectoryEntry* newDirectoryEntry = new DirectoryEntry(indexNodeNumber, newFileName);
        DirectoryEntry* currentDirectoryEntry = new DirectoryEntry();

        while(true)
        {
            // read an entry from the directory
	    status = readdir(dir, currentDirectoryEntry);

	    if(status < 0)
	    {	        
                fprintf (stderr, "error reading directory in creat\n");
	        exit(Kernel::EXIT_F);
	    }
	    else if(status == 0)
	    {
	        // if no entry read, write the new item at the current 
	        // location and break
	        writedir(dir, newDirectoryEntry);
                cout << "nLinks (before): " << indexNode->getNlink() << endl;
                indexNode->setNlink(indexNode->getNlink() + 1);
                cout << "nLinks (after): " << indexNode->getNlink() << endl;
                cout << indexNodeNumber << endl;
	        break;
	    }
        }

        fileSystem->writeIndexNode(indexNode, indexNodeNumber);

        close(dir);
     }
     delete [] parentDir;
     delete [] newFileName;
}


int Kernel::symlink(const char *oldpath, const char *newpath) {
  String PROGRAM_NAME = "symlink";
  short OUTPUT_MODE = 0700;

  // Debug
  //  std::cout << oldpath << endl;
  //  std::cout << newpath << endl;

  // Check oldpath exists
  Stat *oldstat = new Stat();
  int oldstatus = Kernel::stat((String)oldpath, oldstat);
  if (oldstatus < 0) {
    cout << "Old path doesn't exist" << endl;
    Kernel::perror("symlink");
    Kernel::Exit(1);
  }

  // Debug
  //  std::cout << "Old path exists" << std::endl;

  // Create newpath
  int newstatus = Kernel::creat((String)newpath, OUTPUT_MODE | S_IFSYM);
  if (newstatus < 0) {
    Kernel::perror(PROGRAM_NAME);
    fprintf(stderr, "%s%s%s\n", PROGRAM_NAME, ": unable to open output file ", newpath);
    Kernel::Exit(2);
  }

  //  fprintf(stdout, "%s%s\n", "Created newpath: ", (String)newpath);

  // create a buffer for reading directory entries
  DirectoryEntry *directoryEntry = new DirectoryEntry();

  int status = 0;
  short indexNodeNumber = -1;
  int fd = Kernel::open(Kernel::O_RDONLY);
  status = Kernel::readdir(fd, directoryEntry);
  printf("/%s %d\n",directoryEntry->getName(), directoryEntry->getIno());


  // Check newpath was created correctly
  Stat *newstat = new Stat();
  newstatus = Kernel::stat((String)newpath, newstat);
  if (newstatus < 0) {
    Kernel::perror(PROGRAM_NAME);
    fprintf(stderr, "%s%s%s %d\n", PROGRAM_NAME, ": didn't actually make the file. Try running it again! ", newpath, newstatus);
    Kernel::Exit(2);
  }

  // Convert oldpath into byte array
  unsigned int i;
  byte* bytes = new byte[256];
  for (i = 0; i < strlen(oldpath); i++) {
    bytes[i] = (byte)oldpath[i];
  }

  // Write oldpath (as byte array) into newpath
  int writestatus = Kernel::write(newstatus, bytes, i);
  if (writestatus <= 0) {
    Kernel::perror(PROGRAM_NAME);
    fprintf(stderr, "%s%s\n", PROGRAM_NAME, ": error during write to output file");
    Kernel::Exit(3);
  }
 
  short newMode = Kernel::S_IFSYM;
  newstat->setMode(newMode);
  short foundMode = newstat->getMode();

  /*
  if ((foundMode & Kernel::S_IFMPB) == Kernel::S_IFREG) {
    std::cout << "Regular file" << std::endl;
  } else if ((foundMode & Kernel::S_IFMPB) == Kernel::S_IFDIR) {
    std::cout << "Directory" << std::endl;
  } else if ((foundMode & Kernel::S_IFMPB) == Kernel::S_IFSYM) {
    std::cout << "Symlink!" << std::endl;
  } else {
    std::cout << "Unknown" << std::endl;
  }
  */
  Kernel::close(oldstatus);
  Kernel::close(newstatus);

  return 0;
}


int Kernel::unlink(String filePath){

     String PROGRAM_NAME = "rm";

     int status = 0;

     std::string fileName;

     // create std::string from newPath
     std::string pathStr(filePath);

     size_t pos = pathStr.find_last_of('/');

     // extract file name from newPath
     if(pos != std::string::npos)
     {
        fileName.assign(pathStr.begin() + pos + 1, pathStr.end());
     }

     else
     {
        fileName = pathStr;
     }

     // get filePath's parent directory
     pathStr = pathStr.substr(0, pos);

     // convert std::string to char*
     String parentDir = new char[pathStr.length() + 1];
     strcpy(parentDir, pathStr.c_str());

     Stat *statFilePath = new Stat();
     Stat *statParentDir = new Stat();

     // stat the name to get information about the file or directory
     short statusFilePath = Kernel::stat(filePath, statFilePath);
     short statusParentDir = Kernel::stat(parentDir, statParentDir);

     // exit if error
     if((statusFilePath || statusParentDir) < 0)
     {
        Kernel::perror(PROGRAM_NAME);
	Kernel::Exit(1);
     }

     // mask the file type from the mode for originalPath
     short typeFilePath = (short)(statFilePath->getMode() & Kernel::S_IFMT);

     // mask the file type from the mode for parentDir
     short typeParentDir = (short)(statParentDir->getMode() & Kernel::S_IFMT);

     // if filePath is a directory, exit
     if(typeFilePath == Kernel::S_IFDIR)
     {
        cout << "Error: File Path is a Directory" << endl;
	Kernel::perror(PROGRAM_NAME);
	Kernel::Exit(1);
     }

     // if filePath is a file, and newPath's parent directory is valid, continue.
     else if(typeParentDir == Kernel::S_IFDIR)
     {     
        // get inode # of original file
        IndexNode* indexNode = new IndexNode();

        DirectoryEntry* dirEntry = new DirectoryEntry();

        short indexNodeNumber = Kernel::findIndexNode(filePath, indexNode);

        if(indexNodeNumber < 0)
        {
            Kernel::perror(PROGRAM_NAME);
            Kernel::Exit(1);
        }

        FileSystem *fileSystem = openFileSystems[ROOT_FILE_SYSTEM];

        // open the file
        int fd = open(parentDir, O_RDWR);

        if(fd < 0)
        {
	    Kernel::perror(PROGRAM_NAME);
            Kernel::Exit(1);
        }

	FileDescriptor *file = process->openFiles[fd];

	// check to see if the file is a directory
	if((file->getMode() & S_IFMT) != S_IFDIR)
	{
	    // return (ENOTDIR) if a needed directory is not a directory
	    process->errno = ENOTDIR;
	    return -1;
	}

             // indented part responsible for removal of directory entry

             int cnt = 0;

             // loop until we find the index in the directory of the item to remove
	     while(status >= 0)
		{
		    // read an entry from the directory
		    int status = readdir(fd, dirEntry);
		    if(dirEntry->getName() == fileName){
			break;
		    }
		cnt++;
		}

            short blockSize = file->getBlockSize();
	    // read blocks
	    status = file->readBlock((short)(file->getOffset() / blockSize));
	    if(status < 0)
	        return status;

 	    // Replaces the directory entry (based on the index found above) with the value 0 in the data file.
	    dirEntry->resetBytes(file->getBytes(), cnt*DirectoryEntry::DIRECTORY_ENTRY_SIZE % blockSize);

	    // write the updated block
	    status = file->writeBlock((short)(cnt*DirectoryEntry::DIRECTORY_ENTRY_SIZE) / blockSize);
	    if(status < 0)
	        return status;

            // update the file size
           // file->setOffset(cnt*DirectoryEntry::DIRECTORY_ENTRY_SIZE % blockSize);

        // decrease nlink count
        indexNode->setNlink(indexNode->getNlink() - 1);

        cout << "nLinks: " << indexNode->getNlink() << endl;

        if(indexNode->getNlink() == 0)
        {

            //(Note: The following code came from the end of creat)

            // free any blocks currently allocated to the file
            int sysBlockSize = fileSystem->getBlockSize();
            int blocks = (indexNode->getSize() + sysBlockSize - 1)/ sysBlockSize;
            for(int i = 0; i < blocks; i ++)
	    {
	        int address = indexNode->getBlockAddress(i);

                //cout << "address: " << address << endl;

	        if(address != FileSystem::NOT_A_BLOCK)
	        {
	            fileSystem->freeBlock(address);
                    //cout << indexNode->toString() << endl;
	            indexNode->setBlockAddress(i, FileSystem::NOT_A_BLOCK);
	        }
	    }
      
            // update the inode to size 0

	    cout << "Last Instance Removed: Blocks Freed" << endl;

            indexNode->setSize(0);

        }

        // write the inode to the file system.
        fileSystem->writeIndexNode(indexNode, indexNodeNumber);
     
        close(fd);
     }
}
